
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sstream>

#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>

#include <boost/thread/mutex.hpp>
#include <boost/thread/xtime.hpp>

#include "bocat/common/ini.hpp"
#include "bocat/common/time.hpp"
#include "bocat/common/logging.hpp"

namespace  bocat {
namespace  common {


struct	LogConfigGeneral
{
	std::string	path;

	bool	error_enabled;
	bool	info_enabled;
	bool	debug_enabled;

	int		rotate_files;
	int		rotate_size;

	#define	DEFAULT_ROTATE_FILES	4
	#define	DEFAULT_ROTATE_SIZE		64 * 1024 * 1024

	LogConfigGeneral()
		:	error_enabled(true), info_enabled(true), debug_enabled(false),
			rotate_files( DEFAULT_ROTATE_FILES ),
			rotate_size( DEFAULT_ROTATE_SIZE )
	{
	}
};

struct	LogConfigLevel
{
	std::string	filename;
	int		rotate_files;
	int		rotate_size;
	bool	enabled;

	LogConfigLevel()
		:	rotate_files( -1 ),
			rotate_size( -1 ),
			enabled( false )
	{
	}

};	//// struct LogConfigLevel


class	LogConfig
{
public	:
	LogConfig( std::string ini_filename, std::string section )
	{
		parse( ini_filename, section );
	}

public	:
	/*
	 *  [bocat::common::logging]
	 *  # GENERAL configure
	 *  log.path = /path/to/log/file
	 *  log.enable = all
	 *  #log.enable = error,info
	 *
	 *  # ERROR configure
	 *  log.error.enable = true
	 *  log.error.file.name = /path/to/error.log
	 *  log.error.file.rotate = 4
	 *  log.error.file.maxsize = 64 MB
	 *
	 *  # INFO configure
	 *  log.info.enable = true
	 *  log.info.file.name = /path/to/info.log
	 *  log.info.file.rotate = 4
	 *  log.info.file.maxsize = 64 MB
	 *
	 *  # DEBUG configure
	 *  log.debug.enable = true
	 *  log.debug.file.name = /path/to/debug.log
	 *  log.debug.file.rotate = 4
	 *  log.debug.file.maxsize = 64 MB
	 *
	 */
	void	parse( std::string ini_filename, std::string section )
	{
		INIFile	ini( ini_filename );

		// parse GENERAL options
		std::string	log_path = ini[ section ][ "log.path" ];
		if (log_path.size() > 1) {
			general.path = log_path;

			// make sure directroies exists
			// log-path is an log-file-prefix, it may be not a path
			//boost::filesystem::path	_log_path( log_path );
			//boost::filesystem::create_directories( _log_path );
		}
		std::string	log_enabled = ini[ section ][  "log.enable" ];
		if (log_enabled.size() > 1) {
			if ("all" == log_enabled) {
				general.error_enabled = true;
				general.info_enabled = true;
				general.debug_enabled = true;
			}
			else {
				std::vector< std::string >	fields;
				boost::split( fields, log_enabled, boost::is_any_of(","));
				for (size_t i=0; i < fields.size(); ++i) {
					std::string&	field = fields[ i ];
					boost::trim( field );
					if ( ("error" == field) || ("ERROR" == field) ) {
						general.error_enabled = true;
					}
					else if ( ("info" == field) || ("INFO" == field) ) {
						general.info_enabled = true;
					}
					else if ( ("debug" == field) || ("DEBUG" == field) ) {
						general.debug_enabled = true;
					}
				}
			}
		}

		/// TODO : to parse ERROR options

		/// TODO : to parse INFO options

		/// TODO : to parse DEBUG options

		// check valid configure
		if ( (general.path.size() < 1) && (error.filename.size() < 1) ) {
			std::ostringstream	msg;
			msg << "No error logging output directory defined in " << ini_filename;
			throw std::runtime_error( msg.str() );
		}

		// merge all options
		// merge ERROR with GENERAL options
		if ((general.error_enabled) || error.enabled) {
			if (!error.enabled) {
				error.filename = general.path + "error.log";
				error.rotate_files = general.rotate_files;
				error.rotate_size = general.rotate_size;
				error.enabled = true;
			}
		}
		// merge INFO with GENERAL options
		if ((general.info_enabled) || info.enabled) {
			if (!info.enabled) {
				info.filename = general.path + "info.log";
				info.rotate_files = general.rotate_files;
				info.rotate_size = general.rotate_size;
				info.enabled = true;
			}
		}
		// merge DEBUG with GENERAL options
		if ((general.debug_enabled) || debug.enabled) {
			if (!debug.enabled) {
				debug.filename = general.path + "debug.log";
				debug.rotate_files = general.rotate_files;
				debug.rotate_size = general.rotate_size;
				debug.enabled = true;
			}
		}
	}

private	:
	LogConfigGeneral	general;

public	:
	LogConfigLevel		error, info, debug;

};	//// class LogConfig


///////////////////////////////////////////////////////////////////////


class	Appender
{
public	:
	virtual	int	error( int line_no, const char* filename, const char* msg) = 0;
	virtual	int	warn( int line_no, const char* filename, const char* msg) = 0;
	virtual	int	info( int line_no, const char* filename, const char* msg) = 0;
	virtual	int	debug( int line_no, const char* filename, const char* msg) = 0;

public	:
	virtual	~Appender() {}
};	//// class Appender


///////////////////////////////////////////////////////////////////////
//

class	RollingFile
{
	#define	DEFAULT_ROLLING_FILESIZE	1024 * 1024 * 64

public	:
	RollingFile( std::string filename, u_int32_t max_files=4, u_int32_t filesize=DEFAULT_ROLLING_FILESIZE )
		:	_filename(filename), _max_files(max_files), _filesize(filesize),
			_fp(NULL)
	{
	}

	~RollingFile()
	{
		if ( _fp ) {
			fclose( _fp );
			_fp = NULL;
		}
	}


public	:
	FILE*	get( void )
	{
		rotate();

		return	_fp;
	}

private	:
	void	open( void )
	{
		if ( NULL != _fp ) {
			return;
		}

		_fp = fopen( _filename.c_str(), "a" );
		if ( NULL == _fp ) {
			std::ostringstream	msg;
			msg << "fail to open file " << _filename;
			throw std::runtime_error( msg.str() );
		}
		setlinebuf( _fp );
	}

	void	rotate( void )
	{
		open();

		fseek( _fp, 0, SEEK_END );
		long	length = ftell( _fp );
		if ( length < _filesize ) {
			return;
		}

		fclose( _fp );
		
		boost::filesystem::path	this_file( _filename );

		char	buf[ 512 ];
		snprintf(buf,sizeof(buf) - 1, "%s.%d", _filename.c_str(), _max_files);
		boost::filesystem::path	last_file( buf );
		if ( boost::filesystem::exists( last_file ) ) {
			boost::filesystem::remove( last_file );
		}

		boost::filesystem::path	to_file = last_file;
		for ( u_int32_t i=_max_files - 1; i>0; --i ) {
			snprintf(buf,sizeof(buf) - 1, "%s.%d", _filename.c_str(), i);
			boost::filesystem::path	from_file( buf );
			
			if ( boost::filesystem::exists( from_file ) ) {
				boost::filesystem::rename( from_file, to_file );
			}

			to_file = from_file;
		}
		boost::filesystem::rename( this_file, to_file );

		open();
	}

private	:
	std::string	_filename;
	u_int32_t	_max_files;
	u_int32_t	_filesize;

	FILE*	_fp;
};	//// class RollingFile


class	RollingFileAppender	:	public Appender
{
public	:
	RollingFileAppender( LogConfig config )
		:	_config( config )
	{
		_error_enabled = false;
		_info_enabled = false;
		_debug_enabled = false;

		_error_file_p = NULL;
		_info_file_p = NULL;
		_debug_file_p = NULL;

		if (config.error.enabled) {
			_error_file_p = new RollingFile( config.error.filename, config.error.rotate_files, config.error.rotate_size );
			_error_enabled = true;
		}
		if (config.info.enabled) {
			_info_file_p = new RollingFile( config.info.filename, config.info.rotate_files, config.info.rotate_size );
			_info_enabled = true;
		}
		if (config.debug.enabled) {
			_debug_file_p = new RollingFile( config.debug.filename, config.debug.rotate_files, config.debug.rotate_size );
			_debug_enabled = true;
		}
	}

	~RollingFileAppender()
	{
		if ( _error_file_p ) {
			delete _error_file_p;
			_error_file_p = NULL;
		}
		if ( _info_file_p ) {
			delete _info_file_p;
			_info_file_p = NULL;
		}
		if ( _debug_file_p ) {
			delete	_debug_file_p;
			_debug_file_p = NULL;
		}
	}

public	:
	virtual	int	error( int line_no, const char* filename, const char* msg)
	{
		if ( !_error_enabled ) {
			return	0;
		}

		FILE*	fp = _error_file_p->get();
		fprintf( fp, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	warn( int line_no, const char* filename, const char* msg)
	{
		if ( !_error_enabled ) {
			return	0;
		}

		FILE*	fp = _error_file_p->get();
		fprintf( fp, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	info( int line_no, const char* filename, const char* msg)
	{
		if ( !_info_enabled ) {
			return	0;
		}

		FILE*	fp = _info_file_p->get();
		fprintf( fp, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	debug( int line_no, const char* filename, const char* msg)
	{
		if ( !_debug_enabled ) {
			return	0;
		}

		FILE*	fp = _debug_file_p->get();
		fprintf( fp, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

private	:
	LogConfig	_config;

	bool		 _error_enabled;
	RollingFile* _error_file_p;
	bool		 _info_enabled;
	RollingFile* _info_file_p;
	bool		 _debug_enabled;
	RollingFile* _debug_file_p;
};


///////////////////////////////////////////////////////////////////////
//


class	ConsoleAppender	:	public Appender
{

public	:
	ConsoleAppender()
	{
	}

	virtual	~ConsoleAppender()
	{
	}

public	:

	virtual	int	error( int line_no, const char* filename, const char* msg)
	{
		fprintf( stderr, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	warn( int line_no, const char* filename, const char* msg)
	{
		fprintf( stderr, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	info( int line_no, const char* filename, const char* msg)
	{
		fprintf( stdout, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}

	virtual	int	debug( int line_no, const char* filename, const char* msg)
	{
		fprintf( stdout, "%s %s:%d %s\n", bocat::common::now(), filename, line_no, msg );

		return	0;
	}
};	// class ConsoleAppender

class   TinyLogger  :   public Logger
{

public  :
    TinyLogger()
    {
        _error_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _warn_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _info_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _debug_msg_p = new char[ SIZE_MESSAGE_BUF ];

        _configured = false;
		_appender_p = NULL;
    }

    virtual ~TinyLogger()
    {
		if ( _appender_p ) {
			delete _appender_p;
		}

		delete [] _error_msg_p;
		delete [] _warn_msg_p;
		delete [] _info_msg_p;
		delete [] _debug_msg_p;
    }

public  :
    virtual int error(int line_no, const char* file_name, const char* format, ...) 
    {
		boost::mutex::scoped_lock lock(_error_mutex);

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_error_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_error_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _error_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
			_appender_p->error( line_no, file, _error_msg_p );
        }

        return 0;
    }

    virtual int warn(int line_no, const char* file_name, const char* format, ...) 
    {
		boost::mutex::scoped_lock lock(_error_mutex);

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_error_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_warn_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _warn_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
			_appender_p->warn( line_no, file, _warn_msg_p );
        }

        return 0;
    }

    virtual int info(int line_no, const char* file_name, const char* format, ...) 
    {
		boost::mutex::scoped_lock lock(_info_mutex);

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_info_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_info_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _info_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
			_appender_p->info( line_no, file, _info_msg_p );
        }

        return 0;
    }

    virtual int debug(int line_no, const char* file_name, const char* format, ...) 
    {
		boost::mutex::scoped_lock lock(_debug_mutex);

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_debug_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_debug_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _debug_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
			_appender_p->debug( line_no, file, _debug_msg_p );
        }

        return 0;
    }

private	:
	const char*	basename(const char* file_name)
	{
		const char*	slash = strrchr(file_name, '/');
		if (slash == NULL) {
			return file_name;
		}
		else {
			return slash + 1;
		}
	}

private :
    void    loadDefaultConfig()
    {
        if (_configured) {
            return;
        }

        _appender_p = new ConsoleAppender();

		_error_enabled = true;
		_info_enabled = true;
		_debug_enabled = false;

        _configured = true;
    }

public  :
    
    void    configure(const char* config_file, const char* section)
    {
		LogConfig	config( config_file, section );

		if (NULL != _appender_p) {
			_appender_p = NULL;
			delete	_appender_p;

			_configured = false;
		}

        _appender_p = new RollingFileAppender( config );

		_error_enabled = config.error.enabled;
		_info_enabled = config.info.enabled;
		_debug_enabled = config.debug.enabled;

        _configured = true;
    }


	bool	errorEnabled(void) { return _error_enabled; }
	bool	infoEnabled(void)  { return _info_enabled; }
	bool	debugEnabled(void) { return _debug_enabled; }

private :  
    
    enum {
        SIZE_MESSAGE_BUF = 2048,
    };

    char*   _error_msg_p;
    char*   _warn_msg_p;
    char*   _info_msg_p;
    char*   _debug_msg_p;

    bool    _configured;

	bool	_error_enabled;
	bool	_info_enabled;
	bool	_debug_enabled;

	Appender*	_appender_p;

	boost::mutex _error_mutex;
	boost::mutex _info_mutex;
	boost::mutex _debug_mutex;
};

///////////////////////////////////////////////////////////////////////
//

static  TinyLogger sg_logger;

Logger* getLogger(const char* category)
{
    return &sg_logger;
}


}   // namespace common
}   // namespace bocat

